using System;
using System.Threading.Tasks;
using Amazon.Runtime;
using anydata.Extensions;
using anydata.Loaders.Builders;
using anydata.Loaders.Extensions;
using anydata.Loaders.ResponseModel;

using MongoDB.Bson;
using MongoDB.Driver;

namespace anydata.Loaders
{
    public class MongoDataSourceLoader : DataSourceLoader
    {
        public static LoadResult Load<TDocument>(IAggregateFluent<TDocument> source, DataSourceLoadOptionsBase options)
        {
            return Task.Run(() => LoadAsync(source, options)).Result;
        }

        public static async Task<LoadResult> LoadAsync<TDocument>(IAggregateFluent<TDocument> source, DataSourceLoadOptionsBase options)
        {
            if (source is null)
                throw new ArgumentNullException(nameof(source));

            if (options is null)
                throw new ArgumentNullException(nameof(options));

            var loadResult = new LoadResult();
            IAggregateFluent<TDocument> result = source;

            // filter
            if (options.filter?.Count > 0)
            {
                FilterDefinition<TDocument> filterDefinition = Builders.FilterOptionBuilder<TDocument>.Build(options.filter);
                result = result.Match(filterDefinition);
            }

            // group
            if (options.group?.Length > 0)
            {
                var key = options.group[0].selector;
                var group = new BsonDocument
                {
                    { "_id", $"${key}" },
                    { "key", new BsonDocument("$first", $"${key}") }
                };
                result = result.Group<TDocument>(group);
            }

            // RequireTotalCount
            if (options.isCountQuery || options.requireTotalCount)
            {
                var countResult = await result.Count().FirstOrDefaultAsync();
                loadResult.totalCount = Convert.ToInt32(countResult?.Count ?? 0);
                if (options.isCountQuery)
                {
                    return loadResult;
                }
            }

            // totalSummary
            if (options.totalSummary?.Length > 0)
            {
                var summary = new BsonDocument("_id", "totalSummary");
                foreach (var item in options.totalSummary)
                {
                    switch (item.summaryType)
                    {
                        case "sum":
                        case "avg":
                        case "min":
                        case "max":
                            summary.Add($"__{item.summaryType}{item.selector}", new BsonDocument($"${item.summaryType}", $"${item.selector}"));
                            break;
                        case "count":
                            summary.Add("__count", new BsonDocument("$sum", 1));
                            break;
                        default:
                            throw new ArgumentException($"(`Invalid summary type '{item.summaryType}'`)");
                    }
                }
                var data = await result.Group<Dictionary<string, object>>(summary).FirstOrDefaultAsync();
                if (data != null && data.Any())
                {
                    var summaryData = new List<object>();
                    foreach (var item in options.totalSummary)
                    {
                        switch (item.summaryType)
                        {
                            case "sum":
                            case "avg":
                            case "min":
                            case "max":
                                summaryData.Add(data.GetValueOrDefault($"__{item.summaryType}{item.selector}", 0));
                                break;
                            case "count":
                                summaryData.Add(data.GetValueOrDefault("__count", 0));
                                break;
                            default:
                                throw new ArgumentException($"(`Invalid summary type '{item.summaryType}'`)");
                        }
                    }
                    loadResult.summary = summaryData.ToArray();
                }
            }

            // sorting
            SortDefinition<TDocument> sortDefinition = Builders.SortOptionBuilder<TDocument>.Build(options, !result.IsSorted());
            if (sortDefinition != null)
            {
                result = result.Sort(sortDefinition);
            }

            // paging
            if (options.skip > 0)
                result = result.Skip(options.skip);

            if (options.take > 0)
                result = result.Limit(options.take);
            else
                result = result.Limit(10240);

            loadResult.data = await result.ToListAsync();

            return loadResult;
        }
    }
}